<?php
/**
 * RPC Server
 * 
 * @category	XML
 * @package		dayscript.xml.rpc
 * @subpackage	
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license		
 * @version		1.0
 * @version		$Revision: 0 $
 * @filesource	
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-13 $
 */

namespace dayscript\xml\rpc;

/**
 * RPC Server
 *
 * @category	XML
 * @package		dayscript.xml.rpc
 * @subpackage	
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license		
 * @version		1.0
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-13 $
 */
class Server	{
	/**
	 * Server callback functions list
	 * @var array
	 */
	protected $callbacks = array( );
	/**
	 * Server message
	 * @var \dayscript\xml\rpc\Message
	 */
	protected $message;
	/**
	 * Server capabilities 
	 * @var array
	 */
	protected $capabilities;
	/**
	 * Server constructor
	 * @param array $callbacks
	 * @param mixed $data
	 */
	public function __construct( $callbacks = null, $data = null )	{
		$this->setCapabilities( );
		if ( $callbacks )
			$this->callbacks = $callbacks;
		
		$this->setCallbacks( );
		$this->serve( $data );
	}
	/**
	 * Gives a Response to $data request, direct to the output
	 * @param mixed $data
	 */
	protected function serve( $data = null )	{
		$result = null;
		if ( !$data )
			$result = new \dayscript\xml\rpc\Exception( 'Request error. No data set or request made.', -32700 ); 
		else	{
			$this->message = new \dayscript\xml\rpc\Message( $data );
			try	{
				$parsed = $this->message->parse( );
				if ( !$parsed )
					$result = new \dayscript\xml\rpc\Exception( 'Parse error. Response not well formed.', -32700 );
			}
			catch ( \dayscript\xml\rpc\Exception $e )	{
				$result = $e;
			}
			
			if ( !$result )	{
				if ( $this->message->getType( ) != 'methodCall' )
					$result = new \dayscript\xml\rpc\Exception( 'Server error. Invalid xml-rpc. Not conforming to spec. Request must be a methodCall', -32600 );
				else
					$result = $this->call( $this->message->getMethodName() , $this->message->getParams( ) );
			}
		}
		
		if ( $result instanceOf \dayscript\xml\rpc\Exception )
			$this->error( $result );
		else	{
			$r = new \dayscript\xml\rpc\data\Value( $result );
			$this->output( '<methodResponse><params><param><value>' . $r->getXml( ) . '</value></param></params></methodResponse>' );
		}
	}
	/**
	 * Server method call
	 * @param string $methodname Method or function to call
	 * @param mixed $args
	 * @return string|\dayscript\xml\rpc\Exception $result
	 */
	public function call( $methodname, $args )	{
		if ( !$this->hasMethod( $methodname ) )
			return new \dayscript\xml\rpc\Exception( 'Server error. Requested method "' . $methodname . '" does not exist.', -32601 );
		
		$method = $this->callbacks[$methodname];
		if ( \count( $args ) == 1 )	{
			$args = $args[0];
		}
		if ( \substr( $method, 0, 5 ) == 'this:' )	{
			$method = \substr( $method, 5 );
			if ( !\method_exists( $this, $method ) )
				return new \dayscript\xml\rpc\Exception( 'Server error. Requested class method "' . $method . '" does not exist.', -32601 );
			$result = $this->$method( $args );
		}
		else	{
			if ( !\function_exists( $method ) )
				return new \dayscript\xml\rpc\Exception( 'Server error. Requested function "' . $method . '" does not exist.', -32601 );
			$result = $method( $args );
		}
		return $result;
	}
	/**
	 * Internal Server error to output
	 * @param string|\dayscript\xml\rpc\Exception $message
	 * @param int $error Error code
	 */
	protected function error( $error, $message = null )	{
		if ( $message && !\is_object( $error ) )
			$error = new \dayscript\xml\rpc\Exception( $message, $error );
		
		$this->output( $error->getXML( ) );
	}
	/**
	 * Sends message to output
	 * @param string $xml
	 */
	protected function output( $xml )	{
		$xml = '<?xml version="1.0"?>' . "\n" . $xml;
		$length = \strlen( $xml );
		header( 'Connection: close' );
		header( 'Content-Length: ' . $length );
		header( 'Content-Type: text/xml' );
		header( 'Date: ' . \date( 'r' ) );
		echo $xml;
	}
	/**
	 * Server method search
	 * @param string $method Method name
	 * @return boolen Wheter the $method exists
	 */
	public function hasMethod( $method )	{
		return \array_key_exists( $method, $this->callbacks );
	}
	/**
	 * Sets the actual server capabilities
	 */
	private function setCapabilities( )	{
		$this->capabilities = array (
            'xmlrpc' => array(
                'specUrl' => 'http://www.xmlrpc.com/spec',
                'specVersion' => 1
			),
            'faults_interop' => array (
                'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
                'specVersion' => 20010516
			),
            'system.multicall' => array (
                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
                'specVersion' => 1
			),
		);
	}
	/**
	 * Returns actual server capabilities
	 * @return array $capabilities
	 */
	public function getCapabilities( )	{
		return $this->capabilities;
	}
	/**
	 * Sets the actual server callback
	 */
	private function setCallbacks( )	{
		$this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
		$this->callbacks['system.listMethods'] = 'this:listMethods';
		$this->callbacks['system.multicall'] = 'this:multiCall';
	}
	/**
	 * Returns a list of actual server methods
	 * @return array $methods
	 */
	public function listMethods( )	{
		return \array_keys( $this->callbacks );
	}
	/**
	 * Sequential calls
	 * @param array $methodcalls
	 * @return array|\dayscript\xml\rpc\Exception 
	 */
	public function multiCall( $methodcalls )	{
		$return = array( );
		foreach ( $methodcalls as $call )	{
			$method = $call['methodName'];
			$params = $call['params'];
			if ( $method == 'system.multicall' )
				$result = new \dayscript\xml\rpc\Exception( 'Recursive calls to system.multicall are forbidden.', -32600 );
			else
				$result = $this->call( $method, $params );
			
			if ( $result instanceOf \dayscript\xml\rpc\Exception )	{
				$return[] = array (
                    'faultCode' => $result->code,
                    'faultString' => $result->message
				);
			}
			else
				$return[] = array( $result );
		}
		return $return;
	}
}
